Skip to content

Conversation

@innocenzi
Copy link
Member

@innocenzi innocenzi commented Jul 30, 2025

Closes #1389, #266

This pull request updates the validator to make validation error messages provide more context and be localizable.

A few APIs change in this pull request, so it is breaking. The first one is that the Validator now needs a Translator instance. The recommended way of working with the validator is now to use dependency injection, instead of manually instantiating it.

The Rule class no longer requires a message() method. Instead, rule classes that also implement the HasErrorMessage may have a hardcoded validation error message, and more importantly, rule classes that implement HasTranslationVariables must return translation variables used in translation files.

Tempest now provides a localization.en.yml translation file with default translation messages. This builds on the recently-introduced localization feature, using MessageFormat as the translation message syntax. For instance, validation error message for AfterDate looks as follows:

after_date: |
  .input {$field :string}
  .input {$inclusive :boolean}
  .input {$date :datetime date_style=long time_style=none}
  .match $inclusive
    true {{{$field} must be after or equal to {$date}}}
    false {{{$field} must be after {$date}}}

This expects a inclusive and date translation variable, which AfterDate provides through its HasTranslationVariables implementation.

To get the actual error message, the Validator class now has a getErrorMessage method, which accepts a Rule instance and an optional field name.

$after = new AfterDate(date: '2024-01-01', inclusive: true);

$this->validator->getErrorMessage($after, field: 'Creation date');
// Creation date must be after or equal to January 1, 2024

This pull request also has some minor fixes regarding our intl message formatter, it should be now more compliant and I added support for the :boolean function, which includes a boolean selector.

@innocenzi
Copy link
Member Author

innocenzi commented Jul 30, 2025

Marking as draft because:

  • @brendt I'd like to rename rules to be more consistent; currently we have different conventions like ShouldBeFalse, IsInteger, or just Email
  • @Treggats That would be the occasion to be involved in our first automatic upgrade!

@Treggats
Copy link
Contributor

@innocenzi can you create an issue so I can get started?

Would help a lot if you have before and after code snippets

@innocenzi
Copy link
Member Author

@Treggats I opened #1445, but I don't have the definitive list of breaking changes, I'm waiting for Brent's input regarding the rule class names.

For now, the main breaking change is that new Validator now requires a Translator instance. I don't think there's other breaking changes than that, but will confirm after PR review

@brendt
Copy link
Member

brendt commented Jul 31, 2025

Ah, I opened a Discord thread before seeing this one :) Let's keep brainstorming in Discord and log our decisions in #1445

@innocenzi
Copy link
Member Author

I'm thinking the migration CLI can be done after this PR is merged? Should we target 2.x for this one or is main fine?

@brendt
Copy link
Member

brendt commented Jul 31, 2025

@innocenzi we should target 2.x with this one

@innocenzi innocenzi changed the base branch from main to 2.x July 31, 2025 13:38
@innocenzi innocenzi marked this pull request as ready for review August 2, 2025 00:12
@innocenzi
Copy link
Member Author

innocenzi commented Aug 2, 2025

@brendt I went ahead and updated the naming convention to use the predicate format, since reserved keywords are an issue otherwise. Let me know what you think

@Treggats here is a rector rule if it helps:

->withConfiguredRule(RenameClassRector::class, [
    'Tempest\Validation\Rules\AlphaNumeric' => 'Tempest\Validation\Rules\IsAlphaNumeric',
    'Tempest\Validation\Rules\ArrayList' => 'Tempest\Validation\Rules\IsArrayList',
    'Tempest\Validation\Rules\BeforeDate' => 'Tempest\Validation\Rules\IsBeforeDate',
    'Tempest\Validation\Rules\Between' => 'Tempest\Validation\Rules\IsBetween',
    'Tempest\Validation\Rules\BetweenDates' => 'Tempest\Validation\Rules\IsBetweenDates',
    'Tempest\Validation\Rules\Count' => 'Tempest\Validation\Rules\HasCount',
    'Tempest\Validation\Rules\DateTimeFormat' => 'Tempest\Validation\Rules\HasDateTimeFormat',
    'Tempest\Validation\Rules\DivisibleBy' => 'Tempest\Validation\Rules\IsDivisibleBy',
    'Tempest\Validation\Rules\Email' => 'Tempest\Validation\Rules\IsEmail',
    'Tempest\Validation\Rules\EndsWith' => 'Tempest\Validation\Rules\EndsWith',
    'Tempest\Validation\Rules\Even' => 'Tempest\Validation\Rules\IsEvenNumber',
    'Tempest\Validation\Rules\HexColor' => 'Tempest\Validation\Rules\IsHexColor',
    'Tempest\Validation\Rules\IP' => 'Tempest\Validation\Rules\IsIP',
    'Tempest\Validation\Rules\IPv4' => 'Tempest\Validation\Rules\IsIPv4',
    'Tempest\Validation\Rules\IPv6' => 'Tempest\Validation\Rules\IsIPv6',
    'Tempest\Validation\Rules\In' => 'Tempest\Validation\Rules\IsIn',
    'Tempest\Validation\Rules\Json' => 'Tempest\Validation\Rules\IsJsonString',
    'Tempest\Validation\Rules\Length' => 'Tempest\Validation\Rules\HasLength',
    'Tempest\Validation\Rules\Lowercase' => 'Tempest\Validation\Rules\IsLowercase',
    'Tempest\Validation\Rules\MACAddress' => 'Tempest\Validation\Rules\IsMacAddress',
    'Tempest\Validation\Rules\MultipleOf' => 'Tempest\Validation\Rules\IsMultipleOf',
    'Tempest\Validation\Rules\NotEmpty' => 'Tempest\Validation\Rules\IsNotEmptyString',
    'Tempest\Validation\Rules\NotIn' => 'Tempest\Validation\Rules\IsNotIn',
    'Tempest\Validation\Rules\NotNull' => 'Tempest\Validation\Rules\IsNotNull',
    'Tempest\Validation\Rules\Numeric' => 'Tempest\Validation\Rules\IsNumeric',
    'Tempest\Validation\Rules\Odd' => 'Tempest\Validation\Rules\IsOddNumber',
    'Tempest\Validation\Rules\Password' => 'Tempest\Validation\Rules\IsPassword',
    'Tempest\Validation\Rules\PhoneNumber' => 'Tempest\Validation\Rules\IsPhoneNumber',
    'Tempest\Validation\Rules\RegEx' => 'Tempest\Validation\Rules\MatchesRegEx',
    'Tempest\Validation\Rules\Time' => 'Tempest\Validation\Rules\IsTime',
    'Tempest\Validation\Rules\Timestamp' => 'Tempest\Validation\Rules\IsUnixTimestamp',
    'Tempest\Validation\Rules\Timezone' => 'Tempest\Validation\Rules\IsTimezone',
    'Tempest\Validation\Rules\Ulid' => 'Tempest\Validation\Rules\IsUlid',
    'Tempest\Validation\Rules\Uppercase' => 'Tempest\Validation\Rules\IsUppercase',
    'Tempest\Validation\Rules\Url' => 'Tempest\Validation\Rules\IsUrl',
    'Tempest\Validation\Rules\Uuid' => 'Tempest\Validation\Rules\IsUuid',
])

@innocenzi innocenzi force-pushed the feat/validator-messages branch from 39dd31a to e8f6e05 Compare August 2, 2025 01:42
@Treggats
Copy link
Contributor

Treggats commented Aug 2, 2025

@innocenzi I can use this to create a rule to rename these instances in a codebase :)

@Treggats here is a rector rule if it helps:

->withConfiguredRule(RenameClassRector::class, [
//
])

Copy link
Member

@brendt brendt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a solid PR, I rebased it on 2.x, feel free to double-check and merge when it's green

@innocenzi innocenzi force-pushed the feat/validator-messages branch from bf4b687 to e679026 Compare August 13, 2025 14:05
@innocenzi innocenzi merged commit edea7aa into 2.x Aug 13, 2025
75 checks passed
@innocenzi innocenzi deleted the feat/validator-messages branch August 13, 2025 14:43
@brendt brendt mentioned this pull request Aug 18, 2025
Bapawe pushed a commit to Bapawe/tempest-framework that referenced this pull request Aug 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants